﻿// <copyright file="SimpleTileGdi.cs" company="TenneySoftware">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>Robert Tenney</author>
// <email>rob10e@yahoo.com</email>
// <date>April 2009</date>
// <summary>Simple 2D Tile graphics manager using GDI+ libraries.</summary>

// License:

// Product: Ascii / GDI+ Graphics Manager. Library for RPGRoguelikeEngine .Net.
// Author: Robert N. Tenney
// Copyright: April 2009

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation version 2
// of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// This license covers all source code, binaries, and other materials related to this project
// unless specifically stated otherwise.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.IO;
using System.Windows.Forms;

using TenneySoftware.Engine;
using TenneySoftware.Engine.Debug;
using TenneySoftware.Engine.GraphicsManager;

namespace TenneySoftware.GraphicsManagers {
    /// <summary>
    /// An abstract class that provides simple tile-base drawing to a GDI+ surface.
    /// </summary>
    public abstract partial class SimpleTileGdi : IDisposable {
        #region Fields:
        /// <summary>
        /// True if the images lists were loaded and not generated.
        /// </summary>
        private bool areImagesLoaded;

        /// <summary>
        /// Tiles to draw on the screen.
        /// </summary>
        private TileListCollection drawTiles;

        /// <summary>
        /// True if map is displayed, false if level is displayed.
        /// </summary>
        private bool mapDisplayed;

        /// <summary>
        /// Name of generator, it's description and type of screen drawing library.
        /// </summary>
        private string moduleName, moduleDescription, moduleDisplaySurface;

        /// <summary>
        /// Flag to indicate whether or not to use FOV.
        /// </summary>
        private bool doFieldOfView = true;

        /// <summary>
        /// Flag to indicate whether FOV was recently toggled.
        /// </summary>
        private bool toggledFOV;

        /// <summary>
        /// Flag to indicate whether the level and map images have been generated or not.
        /// </summary>
        private bool generated;

        /// <summary>
        /// Flag to indicate whether the manager has been initialized already.
        /// </summary>
        private bool initialized;

        /// <summary>
        /// Global images to represent the player's image.
        /// This will probably become obsolete once more objects are created.
        /// </summary>
        private TileImages player;

        /// <summary>
        /// Size of the level's display area.
        /// </summary>
        private Size levelDisplaySize;

        /// <summary>
        /// Size of the map's display area.
        /// </summary>
        private Size mapDisplaySize;

        /// <summary>
        /// Size of the tiles used for the level.
        /// </summary>
        private Size tileSizeLevel = new Size(16, 16);

        /// <summary>
        /// Size of the tiles used for the map.
        /// </summary>
        private Size tileSizeMap = new Size(8, 8);

        /// <summary>
        /// Flag that indicates that there has been a change on the level, so an
        /// update is needed.
        /// </summary>
        private bool changed;

        /// <summary>
        /// Flag that indicates that the images generated are no longer valid and a
        /// new set needs to be generated.
        /// </summary>
        private bool invalidate;

        /// <summary>
        /// The engine that is call the manager. The engine is the bridge between the
        /// generators and the manager, so the manager needs to be able to reference
        /// the engine.
        /// </summary>
        private RPGEngine engine;

        /// <summary>
        /// The class responsible for interfacing with the GDI+ libraries.
        /// This class is used to simplify drawing.
        /// </summary>
        private GraphicsDevice device;

        /// <summary>
        /// The graphics buffer to which the images are drawn onto.
        /// </summary>
        private BufferedGraphics buffer;

        /// <summary>
        /// The stop watch's start time. Used for calculating the Frames Per Second.
        /// </summary>
        private double ptlu = Stopwatch.GetTimestamp();

        /// <summary>
        /// The number of times the frames counter is called.
        /// </summary>
        private int callCount;

        /// <summary>
        /// The last frame rate calculated.
        /// </summary>
        private int currentFrameRate;

        /// <summary>
        /// Messages to send to the debug console.
        /// </summary>
        private string message;

        /// <summary>
        /// Grid of screen cells that are redrawn only if there has been a change to them.
        /// </summary>
        private ScreenCell[][] screenGrid;

        /// <summary>
        /// Location of the active tile. The tile selected by the engine.
        /// </summary>
        private Point activeTile;

        /// <summary>
        /// Centering mode in use.
        /// </summary>
        private CenteringMode centeringMode = CenteringMode.OnPlayer;

        /// <summary>
        /// Image display offset.
        /// </summary>
        private Point offset;

        /// <summary>
        /// The location that represents the center of the display.
        /// </summary>
        private Point centerPoint = Point.Empty;

        /// <summary>
        /// The location of the cell that is in the center of the display.
        /// </summary>
        private Point centerCell = Point.Empty;

        /// <summary>
        /// Debug modes processor.
        /// </summary>
        private DebugModeProcessor processor;
        #endregion

        #region Constructors:
        /// <summary>
        /// Initializes a new instance of the SimpleTileGdi class.
        /// </summary>
        protected SimpleTileGdi() {
            this.drawTiles = new TileListCollection();
            this.InitializeDebugProcessor();
        }
        #endregion

        #region Properties:
        /// <summary>
        /// Gets or sets the tiles to draw to the screen.
        /// </summary>
        /// <value>
        /// The tiles to draw to the screen.
        /// </value>
        public TileListCollection DrawTiles {
            get {
                return this.drawTiles;
            }

            set {
                this.drawTiles = value;
            }
        }

        /// <summary>
        /// Gets or sets the Centering Mode to use for this instance.
        /// </summary>
        /// <value>
        /// The Centering Mode.
        /// </value>
        public CenteringMode CenteringModeUsed {
            get {
                return this.centeringMode;
            }

            set {
                this.centeringMode = value;
            }
        }

        /// <summary>
        /// Gets the TileImages of the player.
        /// </summary>
        /// <value>
        /// The TileImages of the player.
        /// </value>
        public TileImages PlayerImage {
            get {
                return this.player;
            }
        }

        /// <summary>
        /// Gets the location of the currently active tile.
        /// </summary>
        /// <value>The location of the currently active tile.</value>
        public Point ActiveTile {
            get {
                return this.activeTile;
            }
        }

        /// <summary>
        /// Gets the frame rate.
        /// </summary>
        /// <value>
        /// The frame rate.
        /// </value>
        public int FrameRate {
            get {
                return this.currentFrameRate;
            }
        }

        /// <summary>
        /// Gets or sets the standard messages to display on the screen.
        /// </summary>
        /// <value>
        /// The standard messages to display on the screen.
        /// </value>
        public string Message {
            get {
                return this.message;
            }

            set {
                this.message = value;
            }
        }

        /// <summary>
        /// Gets or sets the RPG engine to interact with.
        /// </summary>
        /// <value>
        /// The RPG engine to interact with.
        /// </value>
        public RPGEngine Engine {
            get {
                return this.engine;
            }

            set {
                this.engine = value;
            }
        }

        /// <summary>
        /// Gets or sets the tile size for drawing tiles on the level display.
        /// </summary>
        /// <value>
        /// The tile size for drawing tiles on the level display.
        /// </value>
        public Size TileSizeLevel {
            get {
                return this.tileSizeLevel;
            }

            set {
                this.tileSizeLevel = value;
            }
        }

        /// <summary>
        /// Gets or sets the tile size for drawing tiles on the map display.
        /// </summary>
        /// <value>
        /// The tile size for drawing tiles on the map display.
        /// </value>
        public Size TileSizeMap {
            get {
                return this.tileSizeMap;
            }

            set {
                this.tileSizeMap = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether or not the images have been generated.
        /// </summary>
        /// <value>
        /// Flag indicating whether or not the images have been generated.
        /// </value>
        public bool Generated {
            get {
                return this.generated;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether or not to use FOV/FOW information during drawing.
        /// </summary>
        /// <value>
        /// Flag to indicate whether or not to use FOV/FOW information during drawing.
        /// </value>
        public bool DoFieldOfView {
            get {
                return this.doFieldOfView;
            }

            set {
                if (this.doFieldOfView != value) {
                    this.toggledFOV = true;
                } else {
                    this.toggledFOV = false;
                }

                this.doFieldOfView = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the FOV was recently toggled.
        /// </summary>
        /// <value>
        /// Flag that indicates whether the FOV was recently toggled.
        /// </value>
        public bool ToggledFieldOfView {
            get {
                return this.toggledFOV;
            }
        }

        /// <summary>
        /// Gets a value indicating whether there was a call to change the images.
        /// </summary>
        /// <value>
        /// Flag that indicates whether there was a call to change the images.
        /// </value>
        public bool Changed {
            get {
                return this.changed;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the level view is the currently active display.
        /// </summary>
        /// <value>
        /// Flag to see if the level view is the currently active display.
        /// </value>
        public bool LevelDisplayActive {
            get {
                return !this.mapDisplayed;
            }
        }

        /// <summary>
        /// Gets the name of this module.
        /// </summary>
        /// <value>
        /// The name of this module.
        /// </value>
        public string ModuleName {
            get {
                return this.moduleName;
            }

            protected set {
                this.moduleName = value;
            }
        }

        /// <summary>
        /// Gets the description of this module.
        /// </summary>
        /// <value>
        /// The description of this module.
        /// </value>
        public string ModuleDescription {
            get {
                return this.moduleDescription;
            }

            protected set {
                this.moduleDescription = value;
            }
        }

        /// <summary>
        /// Gets the type of screen this module will draw onto.
        /// </summary>
        /// <value>
        /// The type of screen this will draw onto.
        /// </value>
        public string ModuleDisplaySurface {
            get {
                return this.moduleDisplaySurface;
            }

            protected set {
                this.moduleDisplaySurface = value;
            }
        }

        /// <summary>
        /// Gets or sets the player's images.
        /// </summary>
        /// <value>
        /// The player's images.</value>
        protected TileImages Player {
            get {
                return this.player;
            }

            set {
                this.player = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the images have been loaded or generated.
        /// </summary>
        /// <value>
        /// A value indicating whether the images have been loaded or generated.
        /// </value>
        protected bool AreImagesLoaded {
            get {
                return this.areImagesLoaded;
            }

            set {
                this.areImagesLoaded = value;
            }
        }

        /// <summary>
        /// Gets or sets the debug mode processor.
        /// </summary>
        /// <value>
        /// The debug mode processor.
        /// </value>
        protected DebugModeProcessor Processor {
            get {
                return this.processor;
            }
            
            set {
                this.processor = value;
            }
        }
        #endregion

        #region Public Methods:
        /// <summary>
        /// Disposes the resources used by this instance.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "This is done by child class")]
        public abstract void Dispose();

        /// <summary>
        /// Offsets the level and map images by the given point value. This is cumulative.
        /// </summary>
        /// <param name="offset">Point to offset images.</param>
        public void OffsetDisplay(Point offset) {
            Size tileSize = this.mapDisplayed ? this.tileSizeMap : this.tileSizeLevel;
            this.offset.X -= offset.X / (tileSize.Width / 2);
            this.offset.Y -= offset.Y / (tileSize.Height / 2);
        }

        /// <summary>
        /// Sets the control to perform drawing functions onto.
        /// </summary>
        /// <param name="displayDevice">Window's control.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Control is check before casting")]
        public void SetGraphicsDevice(object displayDevice) {
            Control control = null;
            if (displayDevice is Control) {
                control = (Control)displayDevice;
            } else {
                throw new ArgumentException("Object needed for this display device is a Windows' Control object.");
            }

            this.device = new GraphicsDevice(control);
            this.buffer = this.device.DeviceBuffer;
            this.device.BackgroundColor = Color.Black;
            this.buffer.Graphics.CompositingQuality = CompositingQuality.HighSpeed;
            this.buffer.Graphics.InterpolationMode = InterpolationMode.Low;
            this.buffer.Graphics.SmoothingMode = SmoothingMode.None;
            this.buffer.Graphics.PixelOffsetMode = PixelOffsetMode.None;
            this.buffer.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            this.changed = true;
            this.mapDisplaySize = this.levelDisplaySize = control.Size;
            if (this.initialized) {
                this.Reset();
            }

            this.Invalidate();
            this.Update();
        }

        /// <summary>
        /// Resets the graphics manager.
        /// </summary>
        public void Reset() {
            this.Initialize();
            this.changed = true;
            this.Invalidate();
        }

        /// <summary>
        /// Calling this method will cause all the images to be redrawn, ignoring any flags to skip a cell or tile that
        /// does not require redrawing.
        /// </summary>
        public void Invalidate() {
            this.invalidate = true;
        }

        /// <summary>
        /// Sets the active tile. The location can be given in either tile location or screen location. If the location is
        /// a screen location then the following bool needs to be set to true, otherwise it needs to be set to false.
        /// </summary>
        /// <param name="location">Location to set as active tile.</param>
        /// <param name="screen">True if the location given was from the display's location, or false if it is a tile's location.
        /// </param>
        public void SetActiveTile(Point location, bool screen) {
            if (screen) {
                Size tileSize = this.mapDisplayed ? this.TileSizeMap : this.TileSizeLevel;
                Size displaySize = this.mapDisplayed ? this.mapDisplaySize : this.levelDisplaySize;
                Point centerScreen = new Point((displaySize.Width >> 1) - (tileSize.Width >> 1), (displaySize.Height >> 1) - (tileSize.Height >> 1));
                Point offset = new Point(location.X - centerScreen.X, location.Y - centerScreen.Y);
                Point activeTileInImage = new Point(this.centerPoint.X * tileSize.Width, this.centerPoint.Y * tileSize.Height);
                activeTileInImage.Offset(offset);
                this.activeTile = new Point(activeTileInImage.X / tileSize.Width, activeTileInImage.Y / tileSize.Height);
            } else {
                this.activeTile = location;
            }
        }

        /// <summary>
        /// Causes updating of images. This will determine if any changes made and if so, it will only update those cells.
        /// </summary>
        public void Update() {
            // Find the current framerate.
            this.Framecounter();

            // If the level is not ready yet, leave.
            if (this.Engine.Level == null) {
                return;
            }

            // If this manager was not yet initialized, then do so.
            if (!this.initialized) {
                this.Initialize();
            }

            this.DoFieldOfView = !this.processor.DebugModes.GetValue("DisableFOV");
            Size tileSize = this.mapDisplayed ? this.TileSizeMap : this.TileSizeLevel;
            Size displaySize = this.mapDisplayed ? this.mapDisplaySize : this.levelDisplaySize;
            Point centerDisplay;
            int centerX = 0;
            int centerY = 0;
            bool isSet = false;
            Rectangle cellRectangle;
            Point centerDisplayLocation;
            Point centerDisplayOffset;
            int x1, y1, x2, y2;
            bool end;
            Point startCheck;
            Point offsetter;
            Pen cellOutline;
            Point activeOffset;
            ScreenCell cell;
            Rectangle cellRect;

            // Check what kind of centering mode we are using and set the centerPoint accordingly.
            switch (this.centeringMode) {
                case CenteringMode.OnPlayer:
                    // Center point is the player location.
                    this.centerPoint = this.engine.PlayerLocation;
                    break;
                case CenteringMode.OnScreen:
                    // Center point is the center of the screen.
                    this.centerPoint = new Point((this.engine.Level.Width >> 1) + 1, (this.engine.Level.Height >> 1) + 1);
                    break;
                case CenteringMode.UserDefined:
                    // If not yet set, then the offset is the previously used centerPoint.
                    if (this.offset.IsEmpty) {
                        this.offset = this.centerPoint;
                    } else {
                        // Then the centerPoint is the location that was offset.
                        this.centerPoint = this.offset;
                    }

                    break;
            }

            centerDisplay = new Point(this.centerPoint.X * tileSize.Width, this.centerPoint.Y * tileSize.Height);

            // Iterate through the whole screengrid to determine which one has the centerPoint. Also, if the fov was recently
            // toggled, then all of the cells will need to be updated to show that.
            for (int iterY = 0; iterY < this.screenGrid.GetLength(0); iterY++) {
                for (int iterX = 0; iterX < this.screenGrid[iterY].GetLength(0); iterX++) {
                    cell = this.screenGrid[iterY][iterX];
                    if (cell.CellRectangle.Contains(this.centerPoint)) {
                        centerX = iterX;
                        centerY = iterY;

                        // If this is not a toggle redraw, then we don't need to keep scanning, so set a flag to quit this.
                        if (!this.toggledFOV) {
                            isSet = true;
                            break;
                        }
                    }

                    // If toggled, then generate new images.
                    if (this.toggledFOV) {
                        cell.GenerateImageCell();
                    }
                }

                if (isSet) {
                    break;
                }
            }

            this.centerCell = new Point(centerX, centerY);
            cellRectangle = this.mapDisplayed ? this.screenGrid[centerY][centerX].CellDisplayRectangleMap : this.screenGrid[centerY][centerX].CellDisplayRectangleLevel;
            centerDisplayLocation = cellRectangle.Location;
            centerDisplayOffset = new Point(
                (centerDisplay.X - centerDisplayLocation.X) * -1,
                (centerDisplay.Y - centerDisplayLocation.Y) * -1);
            centerDisplayOffset.Offset((displaySize.Width >> 1) - (tileSize.Width >> 1), (displaySize.Height >> 1) - (tileSize.Height >> 1));

            // Load intial value of the center cell to the display area scanner.
            x1 = centerX;
            y1 = centerY;
            x2 = centerX;
            y2 = centerY;
            end = false;
            startCheck = centerDisplayOffset;

            // Scan from center to left, then to right, then up, then down to find which cells will fit on the display screen.
            // First, find the first(upper / left) cell that will be displayed and it's offset value.
            do {
                if (startCheck.X > 0 && x1 != 0) {
                    x1--;
                    startCheck.Offset(cellRectangle.Width * -1, 0);
                } else {
                    end = true;
                }
            } while (!end);
            end = false;
            do {
                if (startCheck.Y > 0 && y1 != 0) {
                    y1--;
                    startCheck.Offset(0, cellRectangle.Height * -1);
                } else {
                    end = true;
                }
            } while (!end);
            end = false;

            // Then find the last (lower / right) cell to be displayed.
            Point endCheck = centerDisplayOffset;
            do {
                if (endCheck.X + cellRectangle.Width <
                        displaySize.Width &&
                        x2 != (this.screenGrid[centerY].GetLength(0) - 1)) {
                    x2++;
                    endCheck.Offset(cellRectangle.Width, 0);
                } else {
                    end = true;
                }
            } while (!end);
            end = false;
            do {
                if (endCheck.Y + cellRectangle.Height <
                        displaySize.Height &&
                        y2 != (this.screenGrid.GetLength(0) - 1)) {
                    y2++;
                    endCheck.Offset(0, cellRectangle.Height);
                } else {
                    end = true;
                }
            } while (!end);

            centerDisplayOffset = startCheck;
            offsetter = centerDisplayOffset;
            this.device.ClearBackground();
            cellOutline = new Pen(Color.FromArgb(127, Color.Red));
            activeOffset = Point.Empty;
            int iterYY = 0;

            // Iterate through the cells from the first cell found to be within the display to the last one.
            for (int drawY = y1; drawY <= y2; drawY++) {
                int iterXX = 0;
                for (int drawX = x1; drawX <= x2; drawX++) {
                    iterXX++;

                    // Get the cell.
                    cell = this.screenGrid[drawY][drawX];

                    // Get the rectagle bounds of the cell based on displayed view.
                    cellRect = this.mapDisplayed ? cell.CellDisplayRectangleMap : cell.CellDisplayRectangleLevel;

                    // Check to see if the current cell contains the active tile location.
                    if (cell.CellRectangle.Contains(this.ActiveTile)) {
                        // If it does, then get and store the location on the display for the tile.
                        activeOffset = offsetter;
                        activeOffset.Offset(
                            (this.ActiveTile.X - cell.CellRectangle.X) * tileSize.Width,
                            (this.ActiveTile.Y - cell.CellRectangle.Y) * tileSize.Height);
                    }

                    // Only redraw the cell if it was invalidated.
                    if (this.invalidate) {
                        cell.GenerateImageCell();
                    }

                    // Place cell image at the appropriate location on the display.
                    this.buffer.Graphics.DrawImageUnscaled(this.mapDisplayed ? cell.GetMapImage : cell.GetLevelImage, offsetter);

                    // Check for debug modes, and draw appropriate debug data.
                    if (cell.Unchanged && this.processor.DebugModes.GetValue("UnchangedCellHiLite")) {
                        this.buffer.Graphics.FillRectangle(cellOutline.Brush, offsetter.X, offsetter.Y, cellRect.Width, cellRect.Height);
                    }

                    if (this.processor.DebugModes.GetValue("CellGrid")) {
                        this.buffer.Graphics.DrawLine(cellOutline, offsetter.X, offsetter.Y, offsetter.X, offsetter.Y + cellRect.Height);
                        this.buffer.Graphics.DrawLine(cellOutline, offsetter.X, offsetter.Y, offsetter.X + cellRect.Width, offsetter.Y);
                        if (drawX == this.screenGrid[drawY].GetLength(0) - 1) {
                            this.buffer.Graphics.DrawLine(cellOutline, offsetter.X + cellRect.Width, offsetter.Y, offsetter.X + cellRect.Width, offsetter.Y + cellRect.Height);
                        }

                        if (drawY == this.screenGrid.GetLength(0) - 1) {
                            this.buffer.Graphics.DrawLine(cellOutline, offsetter.X, offsetter.Y + cellRect.Height, offsetter.X + cellRect.Width, offsetter.Y + cellRect.Height);
                        }
                    }

                    // Set the offset variarable to the next cell in the column.
                    offsetter.Offset(cellRectangle.Width, 0);
                }

                iterYY++;

                // Reset offset variable to original value, and offset just the row value.
                offsetter = centerDisplayOffset;
                offsetter.Offset(0, cellRectangle.Height * iterYY);
            }

            // If invalidate was previously set, unset it.
            if (this.invalidate) {
                this.invalidate = false;
            }

            // Draw the reticle at the location for the active tile.
            if (this.Engine.Selectable) {
                this.buffer.Graphics.DrawImageUnscaled(this.DrawReticle(), activeOffset);
            }

            this.DrawText();

            // Display is now complete, so swap it to the screen.
            this.device.Draw();

            // Reset flags.
            this.changed = false;
            this.toggledFOV = false;
            this.generated = true;
        }

        /// <summary>
        /// Returns a string representation of this object.
        /// </summary>
        /// <returns>This will return the plugin name.</returns>
        public override string ToString() {
            return this.ModuleName;
        }

        /// <summary>
        /// Toggles between level view and map view.
        /// </summary>
        public void ToggleDisplay() {
            if (this.mapDisplayed) {
                this.mapDisplayed = false;
            } else {
                this.mapDisplayed = true;
            }

            this.Invalidate();
        }

        /// <summary>
        /// Called to process the information file associated with this module.
        /// </summary>
        public abstract void GenerateTiles();
        #endregion

        #region Protected Methods:
        /// <summary>
        /// Initializes this manager.
        /// </summary>
        protected void Initialize() {
            if (this.engine == null) {
                throw new RPGEngineObjectMissingException("Engine object was not set.");
            }

            this.invalidate = true;
            this.changed = true;

            string filename = Path.Combine("GraphicsManagers", System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType.Name.ToString() + "TileSet.bin");
            if (this.drawTiles.Count == 0) {
                if (!File.Exists(filename)) {
                    this.areImagesLoaded = false;
                    this.GenerateTiles();
                } else {
                    this.drawTiles = TileListCollection.Load(filename);
                    this.player = this.drawTiles.Find("player");
                    this.areImagesLoaded = true;
                }
            }

            ScreenCell.Manager = this;
            this.screenGrid = ScreenCell.GenerateCells();
            this.initialized = true;
        }
        #endregion

        #region Private Methods:
        /// <summary>
        /// Update the dynamic messages.
        /// </summary>
        private void UpdateMessages() {
            DebugDynamicMessageCollection messages = new DebugDynamicMessageCollection();
            messages.Add(new DebugDynamicMessage("FPS", this.FrameRate.ToString(CultureInfo.InvariantCulture) + " fps", 1, Position.Right));
            messages.Add(new DebugDynamicMessage("CenterCell", "Cell in center: " + this.centerCell.ToString(), 5, Position.Left));
            this.processor.UpdateMessages(messages);
        }

        /// <summary>
        /// Initializes the debug mode processor.
        /// </summary>
        private void InitializeDebugProcessor() {
            DebugMode[] modes = new DebugMode[] {
                new DebugMode("CellGrid", 5, "Displays grid seperating each display cell."), 
                new DebugMode("UnchangedCellHiLite", 0, "Hi-Lites the display cells that have not changed since the last update with red."), 
                new DebugMode("DisableFOV", 0, "Turns off the Field of view / Fog of war.(show entire level within display)."), 
                new DebugMode("FPS", 1, "Displays frames per second."), 
                new DebugMode("CenterCell", 5, "Displays the cell coordinates for the center cell.")
            };

            this.processor = new DebugModeProcessor(modes, "Video");
        }

        /// <summary>
        /// Checks the frame counter.
        /// </summary>
        private void Framecounter() {
            this.callCount++;
            if ((Stopwatch.GetTimestamp() - this.ptlu) >= Stopwatch.Frequency) {
                this.currentFrameRate = this.callCount;
                this.callCount = 0;
                this.ptlu = Stopwatch.GetTimestamp();
            }
        }

        /// <summary>
        /// Method to draw the messages for the debug console onto the screen.
        /// </summary>
        private void DrawText() {
            // Draw debug text.
            Size displaySize = this.mapDisplayed ? this.mapDisplaySize : this.levelDisplaySize;
            string text = string.Empty;
            Font textFont = new Font("Arial", 12, GraphicsUnit.Pixel);
            if (this.Engine.ConsoleModeActive) {
                string targetText = "Target: " + this.Engine.Console.Target.IdentifierDescription + " - " + this.Engine.Console.Target.IdentifierCode;
                Font targetFont = new Font("DejaVu Sans", 16);
                float targetHalfWidth = this.buffer.Graphics.MeasureString(targetText, targetFont).Width / 2;
                float displayHalfWidth = displaySize.Width / 2;
                float targetXposition = displayHalfWidth - targetHalfWidth;
                PointF targetPosition = new PointF(targetXposition, 0);
                this.buffer.Graphics.DrawString(targetText, targetFont, Brushes.White, targetPosition);
                foreach (string outputLine in this.Engine.Console.OutputBuffer) {
                    text += outputLine + Environment.NewLine;
                }

                textFont = new Font("Consolas", 10);
                text += this.Engine.Console.Input;
                string measureString = this.Engine.Console.Input.Replace(' ', '@');
                float fontHeight = this.buffer.Graphics.MeasureString("I", textFont).Height;
                float textHeight = this.buffer.Graphics.MeasureString(text, textFont).Height;
                if (textHeight + fontHeight >= displaySize.Height) {
                    this.Engine.Console.OutputBuffer.RemoveAt(0);
                }

                PointF cursorPosition = new PointF(this.buffer.Graphics.MeasureString(measureString.Substring(0, Engine.Console.Position + 2), textFont).Width - 2, textHeight - fontHeight);
                this.buffer.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(75, Color.White)), new Rectangle(Point.Empty, displaySize));
                this.buffer.Graphics.DrawLine(Pens.Yellow, cursorPosition, new PointF(cursorPosition.X, cursorPosition.Y + fontHeight));
                this.buffer.Graphics.DrawString(text, textFont, Brushes.Black, new Point(1, 1));
                this.buffer.Graphics.DrawString(text, textFont, Brushes.Yellow, Point.Empty);
            } else {
                this.UpdateMessages();
                ////                int index = 0;
                SizeF stringSize = SizeF.Empty;
                PointF stringPosition = PointF.Empty;
                float displayWidthOffset = 0;
                int leftPosition = 0, centerPosition = 0, rightPosition = 0;

                foreach (DebugBuffer buf in DebugBuffer.Messages) {
                    text = buf.Message;
                    stringSize = this.buffer.Graphics.MeasureString(text, textFont);
                    if (buf.Location != Position.Left) {
                        stringSize = this.buffer.Graphics.MeasureString(text, textFont);
                        displayWidthOffset = displaySize.Width;
                        if (buf.Location == Position.Center) {
                            displayWidthOffset /= 2;
                            stringPosition = new PointF(displayWidthOffset - (stringSize.Width / 2), centerPosition * stringSize.Height);
                            centerPosition++;
                        } else {
                            stringPosition = new PointF(displayWidthOffset - stringSize.Width, rightPosition * stringSize.Height);
                            rightPosition++;
                        }
                    } else {
                        stringPosition = new PointF(0, leftPosition * stringSize.Height);
                        leftPosition++;
                    }

                    this.buffer.Graphics.DrawString(text, textFont, Brushes.Black, new PointF(stringPosition.X + 1, stringPosition.Y + 1));
                    this.buffer.Graphics.DrawString(text, textFont, Brushes.Yellow, stringPosition);
                }
            }
        }

        /// <summary>
        /// Draws the reticle for the level image.
        /// </summary>
        /// <returns>A bitmap of the level recticle.</returns>
        private Bitmap DrawReticle() {
            bool valid = true;

            // TODO: May want to fix this for keyboard control as well.
            if (!this.engine.CanTileBeOccupied(this.activeTile) || (!this.engine.GetTile(this.activeTile).HasSeen && this.doFieldOfView)) {
                valid = false;
            } else {
                valid = true;
            }

            Size tileSize = this.mapDisplayed ? this.TileSizeMap : this.TileSizeLevel;
            Bitmap temp = new Bitmap(tileSize.Width, tileSize.Height);
            Graphics gr = Graphics.FromImage(temp);
            int segmentW = (int)((double)tileSize.Width / 3.0);
            int segmentH = (int)((double)tileSize.Height / 3.0);
            Pen reticleValidPen = new Pen(Color.Chartreuse);
            Pen reticleInvalidPen = new Pen(Color.Red);
            Pen usePen = valid ? reticleValidPen : reticleInvalidPen;
            gr.DrawLine(usePen, 0, 0, segmentW, 0);
            gr.DrawLine(usePen, segmentW * 2, 0, tileSize.Width, 0);
            gr.DrawLine(usePen, 0, 0, 0, segmentH);
            gr.DrawLine(usePen, 0, segmentH * 2, 0, tileSize.Height);
            gr.DrawLine(usePen, tileSize.Width - 1, 0, tileSize.Width - 1, segmentH);
            gr.DrawLine(usePen, tileSize.Width - 1, segmentH * 2, tileSize.Width - 1, tileSize.Height);
            gr.DrawLine(usePen, 0, tileSize.Height - 1, segmentW, tileSize.Height - 1);
            gr.DrawLine(usePen, segmentW * 2, tileSize.Height - 1, tileSize.Width, tileSize.Height - 1);
            return temp;
        }
        #endregion
    }
}
